数字电路 - 电路代码与RTL语言

2025年11月5日

目录
目录

编码、译码和选择

编码,译码和选择这三个电路都是组合逻辑电路里的标准模块,就像乐高里的标准零件一样,用处非常大。

编码 (器) - 把分散变集中

1. 核心思想是什么?

2. 普通编码器

3. 优先编码器 (Priority Encoder) - 解决“冲突”


译码 (器) - 把代号变回信号

1. 核心思想是什么?

2. 它是如何工作的?

3. 译码器的妙用


选择 (器) / 多路复用器 (MUX) - 多选一的开关

1. 核心思想是什么?

2. 它是如何工作的?

SY
0I₀
1I₁

3. 选择器的妙用


关系

这三个都是数字系统里不可或缺的基础模块。搞懂了它们各自的使命和工作原理,很多复杂的电路问题都能迎刃而解。


迭代电路与补码加减法器

迭代组合电路

核心思想

一维迭代结构示例

一维迭代结构通常将前一个单元的输出作为后一个单元的输入,实现信息的逐位处理和传递。

二进制加法器

半加器

全加器

行波进位加法器


补码加减法

1. 机器数与带符号数

2. 补码加减法运算

3. 溢出

4. 补码加减法器

通过使用异或门(XOR)来控制加数 B 是否取反,并利用控制信号同时作为最低位的进位输入,可以用一套电路同时实现加法和减法。


Verilog HDL

核心思维转变:从软件到硬件

在学习 Verilog 之前,必须建立一个认知:你不是在写代码,你是在画电路。


模块(Module):电路的“黑盒子”

Verilog 的基本单位是“模块”。你可以把它想象成一块芯片,或者一个封装好的电路板。

基础骨架

module 模块名 (
    // 1. 端口列表:对外的引脚
    input        clk,    // 输入信号
    input  [7:0] a,      // 8位宽的输入信号
    output [7:0] sum     // 8位宽的输出信号
);

    // 2. 内部变量声明:盒子里的连线和存储
    wire [7:0] temp;
    reg  [7:0] result;

    // 3. 功能描述:电路逻辑
    // ... (逻辑代码)

endmodule

关键要素


数据类型:Wire vs Reg (重难点)

这是新手最容易混淆的地方:

类型关键字物理含义赋值场景
线网型wire物理连线。它不存数据,只负责导电。必须配合 assign 语句使用。
寄存器型reg数据存储(类似变量)。保持当前值直到下次赋值。必须在 过程块 (always, initial) 内部被赋值。

💡 记忆口诀:

  • 用了 assign 必须定义为 wire
  • 用了 alwaysinitial 左边被赋值的必须定义为 reg

描述电路的三种方式

PPT 中用 4选1 多路选择器 (MUX) 展示了 Verilog 描述电路的三种层次:

A. 结构化描述 (Structural) - “像搭积木”

B. 数据流描述 (Dataflow) - “写公式”

C. 行为描述 (Behavioral) - “写逻辑”


过程语句:initial 与 always

initial

always (核心)


赋值的深坑:阻塞 vs 非阻塞

这是必考题,也是电路跑飞的常见原因。

阻塞赋值 (=)

非阻塞赋值 (<=)

⚠️ 黄金法则 (Golden Rule):

  1. 描述时序电路(带时钟 clk 的),一律用 <=
  2. 描述组合电路(不带 clk 的),一律用 =
  3. 绝对不要在同一个 always 块里混用这两种赋值。

重要语法细节

条件语句 (if/case)

模块实例化 (Instantiation)

这是实现层次化设计(自顶向下/自底向上)的关键。

// 推荐:名称关联 (.端口名(信号名))
half_adder u1 (
    .a  (signal_a), 
    .b  (signal_b),
    .sum(signal_sum)
);

// 不推荐:位置关联 (容易接错线)
half_adder u2 (signal_a, signal_b, signal_sum);

常用运算符与数字表示

image.png
PPT 给出了详细的表格,这里按优先级从高到低整理(越靠上优先级越高):

类别符号说明例子
取反/非!, ~!逻辑非, ~按位取反!a (真变假), ~a (1010变0101)
算术*, /, %乘、除、取模硬件设计尽量少用除法和取模
算术+, -加、减
移位<<, >>左移、右移a << 2 (左移2位)
关系<, <=, >, >=小于、小于等于…返回结果是 1 或 0
相等==, !=等于、不等于a == b
按位与&每一位都进行与操作4’b1100 & 4’b1010 = 4’b1000
按位异或^, ^~异或、同或
按位或``
逻辑与&&逻辑判断(a>b) && (b>c)
逻辑或``
三目(条件)? :常见a ? b : c (a真则b,否则c)
拼接符{a, b},{}将信号拼接{cout, sum} = a + b 利用拼接位接收进位

实战项目:使用 Verilog 设计带暂停和复位功能的数字秒表(0-9 计数器)

这是一个非常经典的入门级实战项目:带暂停和复位功能的数字秒表(0-9 计数器)

这个例子非常完美,因为它涵盖了 Verilog 学习中最核心的知识点:

  1. 时序逻辑(计数器):用到 always @(posedge clk) 和非阻塞赋值 <=
  2. 组合逻辑(数码管解码):用到 always @(*)case 语句和阻塞赋值 =
  3. 层次化设计(模块实例化):如何把两个小模块连起来。
  4. 控制信号:如何处理复位(Reset)和使能(Enable)。

这里的电路结构图

我们将设计一个顶层模块 Top_Stopwatch,它内部包含两个子模块:

  1. Counter_10:负责核心计数逻辑(0, 1, … 9, 0 …)。
  2. Decoder_7Seg:负责把数字翻译成数码管能亮的信号。
       [顶层模块 Top_Stopwatch]
       |
输入 --+--> [ 子模块1: 计数器 ] --(wire data)---> [ 子模块2: 译码器 ] --+--> 输出
(clk,  |    (Counter_10)                        (Decoder_7Seg)     |    (seg_led)
rst,   |                                                           |
start) +-----------------------------------------------------------+

Verilog 代码分析

代码核心解析
1. 为什么有的地方用 reg,有的用 wire

这是在 Top 模块中最容易晕的地方。

2. 为什么计数器用 <=,译码器用 =
3. 这里的 rst_n 是什么意思?
4. 实例化时的 .a(b) 格式
Counter_10 u_counter (
    .clk     (sys_clk), 
    ...
);
实现代码
1. 子模块一:核心计数器 (Counter_10. v)
点击查看代码
// 模块功能:带异步复位和暂停功能的十进制计数器
module Counter_10 (
    input            clk,      // 时钟信号
    input            rst_n,    // 复位信号 (n表示低电平有效,即0为复位)
    input            en,       // 使能信号 (1=开始计数,0=暂停)
    output reg [3:0] cnt_out   // 4位输出 (0~9只需要4位,最大是1001)
);

    // ======= 时序逻辑电路 =======
    // 黄金法则:时序逻辑用非阻塞赋值 (<=)
    // 敏感列表:时钟上升沿 OR 复位下降沿 触发
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            // 1. 异步复位:最高优先级
            // 当 rst_n 为 0 时,不管时钟怎样,立马清零
            cnt_out <= 4'd0; 
        end
        else if (en) begin
            // 2. 正常计数逻辑:只有 en 为 1 时才工作
            if (cnt_out == 4'd9)
                cnt_out <= 4'd0; // 计满到9,归零
            else
                cnt_out <= cnt_out + 1'b1; // 没满,加1
        end
        // else (en==0) 的情况省略了
        // 在时序逻辑中,如果不写 else,默认保持原值(锁存上一刻的状态),这正是暂停功能想要的
    end

endmodule
2. 子模块二:数码管译码器 (Decoder_7Seg. v)
点击查看代码
// 模块功能:将4位二进制数转换为7段数码管的控制信号
// 假设是共阴极数码管:1亮,0灭
module Decoder_7Seg (
    input  [3:0] data_in,  // 输入的数字 (0-9)
    output reg [6:0] seg_out // 输出的7段段码 (a,b,c,d,e,f,g)
);

    // ======= 组合逻辑电路 =======
    // 黄金法则:组合逻辑用阻塞赋值 (=)
    // 敏感列表:* 代表只要输入(data_in)变化,就执行
    always @(*) begin
        case (data_in)
            // 根据真值表翻译
            // 格式:数字 : 输出 = 7位二进制段码 (对应 gfe_dcba)
            4'd0 : seg_out = 7'b011_1111; // 显示 0
            4'd1 : seg_out = 7'b000_0110; // 显示 1
            4'd2 : seg_out = 7'b101_1011; // 显示 2
            4'd3 : seg_out = 7'b100_1111; // 显示 3
            4'd4 : seg_out = 7'b110_0110; // 显示 4
            4'd5 : seg_out = 7'b110_1101; // 显示 5
            4'd6 : seg_out = 7'b111_1101; // 显示 6
            4'd7 : seg_out = 7'b000_0111; // 显示 7
            4'd8 : seg_out = 7'b111_1111; // 显示 8
            4'd9 : seg_out = 7'b110_1111; // 显示 9
            // 缺省处理:防止生成锁存器(Latch)
            default: seg_out = 7'b000_0000; // 其他情况全灭
        endcase
    end

endmodule
3. 顶层模块:组装电路 (Top_Stopwatch. v)
点击查看代码
// 模块功能:顶层封装,连接计数器和译码器
module Top_Stopwatch (
    input  sys_clk,      // 系统时钟 (比如 FPGA 板上的 50MHz)
    input  sys_rst_n,    // 系统复位按键
    input  sw_start,     // 开始/暂停开关
    output [6:0] led_pins // 连接到物理数码管引脚
);

    // ==== 1. 定义内部连线 (Wire) ====
    // 这根线用来把 计数器的输出 和 译码器的输入 连起来
    // 注意:因为只是连线,不存数据,所以必须用 wire
    wire [3:0] current_num; 

    // ==== 2. 实例化计数器 (就像把芯片插在板子上) ====
    Counter_10 u_counter (
        .clk     (sys_clk),      // 端口连接:模块端口(外部信号)
        .rst_n   (sys_rst_n),
        .en      (sw_start),
        .cnt_out (current_num)   // 输出连到内部网线 current_num 上
    );

    // ==== 3. 实例化译码器 ====
    Decoder_7Seg u_decoder (
        .data_in (current_num),  // 输入从内部网线 current_num 取值
        .seg_out (led_pins)      // 结果直接送到芯片外部引脚
    );

endmodule